﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/* GraphemeClusterAlgorithm.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System.Collections.Generic;

using AM.Skia.RichTextKit.Utils;

#endregion

#nullable enable

namespace AM.Skia.RichTextKit;

internal static class GraphemeClusterAlgorithm
{
    /// <summary>
    /// Given a sequence of code points, return its grapheme cluster boundaries
    /// </summary>
    /// <param name="codePoints">The code points</param>
    /// <returns>An enumerable of grapheme cluster boundaries</returns>
    public static IEnumerable<int> GetBoundaries (Slice<int> codePoints)
    {
        for (var i = 0; i <= codePoints.Length; i++)
        {
            if (IsBoundary (codePoints, i))
            {
                yield return i;
            }
        }
    }

    /// <summary>
    /// Check if a position in a code point buffer is a grapheme cluster boundary
    /// </summary>
    /// <param name="codePoints">The code points</param>
    /// <param name="position">The position to check</param>
    /// <returns></returns>
    public static bool IsBoundary (Slice<int> codePoints, int position)
    {
        if (codePoints.Length == 0)
        {
            return false;
        }

        // Get the grapheme cluster class of the character on each side
        var a = position <= 0
            ? GraphemeClusterClass.SOT
            : UnicodeClasses.GraphemeClusterClass (codePoints[position - 1]);
        var b = position < codePoints.Length
            ? UnicodeClasses.GraphemeClusterClass (codePoints[position])
            : GraphemeClusterClass.EOT;

        // Rule 11 - Special handling for ZWJ in extended pictograph
        if (a == GraphemeClusterClass.ZWJ)
        {
            var i = position - 2;
            while (i >= 0 && UnicodeClasses.GraphemeClusterClass (codePoints[i]) == GraphemeClusterClass.Extend)
            {
                i--;
            }

            if (i >= 0 && UnicodeClasses.GraphemeClusterClass (codePoints[i]) == GraphemeClusterClass.ExtPict)
            {
                a = GraphemeClusterClass.ExtPictZwg;
            }
        }

        // Special handling for regional indicator
        // Rule 12 and 13
        if (a == GraphemeClusterClass.Regional_Indicator)
        {
            // Count how many
            var count = 0;
            for (var i = position - 1; i > 0; i--)
            {
                if (UnicodeClasses.GraphemeClusterClass (codePoints[i - 1]) != GraphemeClusterClass.Regional_Indicator)
                {
                    break;
                }

                count++;
            }

            // If odd, switch from RI to Any
            if ((count % 2) != 0)
            {
                a = GraphemeClusterClass.Any;
            }
        }


        return pairTable[(int)b][(int)a] != 0;
    }

    private static byte[][] pairTable = new[]
    {
//                          Any   CR   LF   Control   Extend   Regional_Indicator   Prepend   SpacingMark   L   V   T   LV   LVT   ExtPict   ZWJ   SOT   EOT   ExtPictZwg
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // Any
        new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // CR
        new byte[] { 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // LF
        new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // Control
        new byte[] { 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, }, // Extend
        new byte[] { 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // Regional_Indicator
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // Prepend
        new byte[] { 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, }, // SpacingMark
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // L
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, }, // V
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, }, // T
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // LV
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // LVT
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, }, // ExtPict
        new byte[] { 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, }, // ZWJ
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // SOT
        new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // EOT
        new byte[] { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, // ExtPictZwg
    };
}
